import { Vector3 } from "@/utils/Vector3"
import { Text } from "@/js/components/Text"
import { LinearLayout } from "../../Layout"
import { LinkNode, LinkNodeRightArrow } from "../../LinkNode"
import { GraphNode } from "../../GraphNode"
import { Input } from "../../Input"
import { GraphView } from "../../GraphView"
import { CustomElement } from "../../CustomElement"
import { Group } from "../../Group"
import { Menu } from "@/utils/Menu"
import { uuid as Uuid } from "@/utils/uuid"
/**
 * 节点输入模型基类
 */


export class InputLinkNode extends LinearLayout {
    linkNode?: LinkNode
    input?: Input | CustomElement
    desc?: Text

    name: string
    graphNode: GraphNode
    graphView: GraphView
    model: string
    options: InputLinkNodeOptions

    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions) {
        super()
        options = {
            showInput: true,
            showDesc: true,
            showLinkNode: true,
            input: Input,
            linkNode: LinkNode,
            ...options
        }
        this.options = options
        this.graphNode = graphNode
        this.graphView = graphView

        this.options.showLinkNode &&( this.linkNode = new (options.linkNode as any)(this.graphNode, this.graphView) )
        this.options.showInput &&( this.input = new (options.input as any)() )
        this.options.showDesc &&( this.desc = new Text( this.options.desc || this.options.name) ).setFontSize("10px")

        if(this.options.name instanceof Function) this.name = this.options.name()
        else this.name = this.options.name
        this.itemsCenter()
        this.model = this.options.model
        
        this.options.model === "entry" ? this.initEntry() : this.initOutput()

        if( this.linkNode ){
            options.linkNodeBgColor && this.linkNode.setBgColor(options.linkNodeBgColor)
            this.linkNode.setTargetData(this)
            this.linkNode.maxCount = options.linkMaxCount || 1
            this.linkNode.addRule( 
                options.addRule 
                ? options.addRule.bind(this) 
                : (linkNode: LinkNode)=> linkNode.targetData.options.type ===  this.options.type || linkNode.targetData.options.type === 'any')
        }

        this.margin("5px 0")

        if(this.input){
            this.input?.setStyle({
                width: "50px",
                outline: 'none',
                background: '#545454',
                padding: '1px 5px',
                fontSize: '12px',
                color: this.linkNode?.bgColor || "#fff",
                fontWeight: '600'
            })
            this.input.placeholder(options.placeholder || '')
            this.input.setValue(options.inputValue || '')
            options.isBindValue === false || this.input.bindValue( options.model === 'entry'? graphNode.userData.enters : graphNode.userData.outputs, options.name)
        }

        this.setAttribute("title", options.type)
    }

    getValue(){
        return this.input?.getValue()
    }

    setValue(v: any){
        return this.input?.setValue(v)
    }

    initEntry(){
        this.linkNode && this.linkNode.setIndex(1)
        this.linkNode && this.appendChild(this.linkNode)
        this.options.showInput && this.appendChild((this.input as Input).margin("0 0 0 5px"))
        this.desc && this.appendChild(this.desc.margin("0 0 0 5px"))
        this.linkNode && this.graphNode.addEnter(this.name, this.linkNode)

        this.justifyStart()

        this.input &&( this.graphNode.inputModels.enters[this.name] = this )
    }

    initOutput(){
        this.desc && this.appendChild(this.desc.margin("0 5px 0 0"))
        this.linkNode && this.linkNode.setIndex(0)
        this.options.showInput && this.appendChild((this.input as Input).margin("0 0 0 5px"))
        this.linkNode && this.appendChild(this.linkNode.margin("0 0 0 5px"))
        this.linkNode && this.graphNode.addOutput(this.name, this.linkNode)

        this.justifyEnd()

        this.input &&( this.graphNode.inputModels.outputs[this.name] = this )
    }
}

export class ObjectMapInputLinkNode extends LinearLayout {
    linkNode?: LinkNode
    input?: Input | CustomElement
    desc?: Text

    name: string
    graphNode: GraphNode
    graphView: GraphView
    model: string
    options: InputLinkNodeOptions

    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions) {
        super()
        options = {
            showInput: true,
            showDesc: true,
            showLinkNode: true,
            input: Input,
            linkNode: LinkNode,
            ...options
        }
        this.options = options
        this.graphNode = graphNode
        this.graphView = graphView

        this.options.showLinkNode &&( this.linkNode = new (options.linkNode as any)(this.graphNode, this.graphView) )
        this.options.showInput &&( this.input = new (options.input as any)() )
        this.options.showDesc &&( this.desc = new Text( this.options.desc || this.options.name) ).setFontSize("10px")

        if(this.options.name instanceof Function) this.name = this.options.name()
        else this.name = this.options.name
        this.itemsCenter()
        this.model = this.options.model
        
        this.options.model === "entry" ? this.initEntry() : this.initOutput()

        if( this.linkNode ){
            options.linkNodeBgColor && this.linkNode.setBgColor(options.linkNodeBgColor)
            this.linkNode.setTargetData(this)
            this.linkNode.maxCount = options.linkMaxCount || 1
            this.linkNode.addRule( 
                options.addRule 
                ? options.addRule.bind(this) 
                : (linkNode: LinkNode)=> 
                    linkNode.targetData.options.type ===  this.options.type || linkNode.targetData.options.type === 'any'
            )
        }

        this.margin("5px 0")

        if(this.input){
            this.input?.setStyle({
                width: "50px",
                outline: 'none',
                background: '#545454',
                padding: '1px 5px',
                fontSize: '12px',
                color: this.linkNode?.bgColor || "#fff",
                fontWeight: '600'
            })
            this.input.placeholder(options.placeholder || '')
            this.input.setValue(options.inputValue || '')
            options.isBindValue === false || this.input.bindValue( options.model === 'entry'? graphNode.userData.enters : graphNode.userData.outputs, options.name)
        }

        this.setAttribute("title", options.type)
    }

    getValue(){
        return this.input?.getValue()
    }

    setValue(v: any){
        return this.input?.setValue(v)
    }

    initEntry(){
        this.linkNode && this.linkNode.setIndex(1)
        this.linkNode && this.appendChild(this.linkNode)
        this.options.showInput && this.appendChild((this.input as Input).margin("0 0 0 5px"))
        this.desc && this.appendChild(this.desc.margin("0 0 0 5px"))
        this.linkNode && this.graphNode.addEnter(this.name, this.linkNode)

        this.justifyStart()

        this.input &&( this.graphNode.inputModels.enters[this.name] = this )
    }

    initOutput(){
        this.desc && this.appendChild(this.desc.margin("0 5px 0 0"))
        this.linkNode && this.linkNode.setIndex(0)
        this.options.showInput && this.appendChild((this.input as Input).margin("0 0 0 5px"))
        this.linkNode && this.appendChild(this.linkNode.margin("0 0 0 5px"))
        this.linkNode && this.graphNode.addOutput(this.name, this.linkNode)

        this.justifyEnd()

        this.input &&( this.graphNode.inputModels.outputs[this.name] = this )
    }
}

// number数值型
export class InputLinkNodeNumber extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "number"
        options.linkNodeBgColor = "#00BCD4"
        super(graphNode, graphView, options)

        options.addRule = (linkNode: LinkNode) => {
            const type = linkNode.targetData.options.type
            return ["number", "int", "float", "double"].indexOf(type) > -1
        }

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("number")
        }
    }
}

// int数值型
export class InputLinkNodeInt extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "int"
        options.linkNodeBgColor = "#00BCD4"
        super(graphNode, graphView, options)

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("number")
        }
    }
}

// float数值型
export class InputLinkNodeFloat extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "float"
        options.linkNodeBgColor = "#00BCD4"
        super(graphNode, graphView, options)

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("number")
        }
    }
}

// double数值型
export class InputLinkNodeDouble extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "double"
        options.linkNodeBgColor = "#00BCD4"
        super(graphNode, graphView, options)

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("number")
        }
    }
}

// 布尔型
export class InputLinkNodeBoolean extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "boolean"
        options.linkNodeBgColor = "#2196f3"
        super(graphNode, graphView, options)

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("checkbox")

            this.input?.setStyle({
                width: "20px",
                outline: 'none'
            })
        }
        
    }
}

// 函数节点
export class InputLinkNodeFunction extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "function"
        options.linkNodeBgColor = "#219613"
        super(graphNode, graphView, options)
    }
}

// 对象节点
export class InputLinkNodeObject extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "object"
        options.linkNodeBgColor = "#f1f613"
        super(graphNode, graphView, options)
    }
}

// 通用any节点
export class InputLinkNodeAny extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "any"
        options.linkNodeBgColor = "#aaa"
        options.addRule = (linkNode: LinkNode) => {
            return linkNode.targetData.options.type !== 'flow'
        }
        super(graphNode, graphView, options)
        
    }
}

export class InputLinkNodeDynamics extends InputLinkNodeAny {}

// 字符串型
export class InputLinkNodeString extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "string"
        options.linkNodeBgColor = "#c58b6e"
        super(graphNode, graphView, options)
    }
}

// 颜色输入器
export class InputLinkNodeColor extends InputLinkNodeString {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        super(graphNode, graphView, options)

        if(this.input instanceof Input && !options.inputTypeString){
            this.input?.setType("color")
        }
    }
}

// 流程线
export class InputLinkNodeFlow extends InputLinkNode {
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeOptions){
        options.type = "flow"
        options.linkNodeBgColor = "#ffffff"
        options.showInput = options.showInput ||  false
        options.linkNode = LinkNodeRightArrow
        super(graphNode, graphView, options)
    }
}


type InputLinkNodeGroupOptions = {
    name: string
}


// 可增减节点输入模型组
export class InputLinkNodeGroup extends Group {
    container?: LinearLayout
    layout?: LinearLayout
    graphNode: GraphNode
    graphView: GraphView
    inputLinkNodeClassList: {[k:string]: { class: typeof InputLinkNode, options: InputLinkNodeOptions }} = {}

    data: any = {
        list: []
    }
    count = 0
    options: InputLinkNodeGroupOptions
    list:InputLinkNode[] = []
    constructor(graphNode: GraphNode, graphView: GraphView, options: InputLinkNodeGroupOptions){
        super()
        this.options = options
        this.graphNode = graphNode
        this.graphView = graphView
        this.init()

    }

    addInputLinkNode(name: string ,InputLinkNodeCLass: typeof InputLinkNode, options: InputLinkNodeOptions){
        this.inputLinkNodeClassList[name] = {
            class: InputLinkNodeCLass,
            options: options
        }

        return this
    }

    addInputLinkNodes(options: (InputLinkNodeOptions & {InputLinkNodeCLass: typeof InputLinkNode, title: string})[]){
        options.forEach(option => {
            this.addInputLinkNode(option.title, option.InputLinkNodeCLass, option)
        })

        return this
    }

    init(){
        const layout = new LinearLayout().column()
        this.layout = layout
        this.appendChild( layout )
        
        const container =  new LinearLayout().column()
        this.container = container
        layout.appendChild(container)

        const add = new CustomElement().cursor('pointer').setStyle({
            display: 'inline-block',
            width: 'fit-content'
        })
        add.setHtml(`<svg t="1702116605580" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2551" width="24" height="24"><path d="M544 480h112a16 16 0 0 1 16 16v16a16 16 0 0 1-16 16h-112v112a16 16 0 0 1-16 16h-16a16 16 0 0 1-16-16v-112h-112a16 16 0 0 1-16-16v-16a16 16 0 0 1 16-16h112v-112a16 16 0 0 1 16-16h16a16 16 0 0 1 16 16v112z m-32 320c159.056 0 288-128.944 288-288s-128.944-288-288-288-288 128.944-288 288 128.944 288 288 288z m0 48c-185.568 0-336-150.432-336-336s150.432-336 336-336 336 150.432 336 336-150.432 336-336 336z" fill="#ffffff" p-id="2552"></path></svg>`)
        layout.appendChild( add )

        add.addEventListener("click", (e: any)=>{
            const that = this
            const {clientX, clientY} = e

            const list = [
                {
                    name: "选择类型",
                    items: Object.keys(this.inputLinkNodeClassList).map(key=>{
                        return {
                            style:{
                                fontSize: '12px',
                                minWidth: '100px',
                                padding: '2px 5px'
                            },
                            name: key,
                            click({close}:any){
                                that.add(that.inputLinkNodeClassList[key], key)
                                close()
                            }
                        }
                    })
                }
            ]

            new Menu(list).show(clientX, clientY)
        })

    }

    private add(item: { class: typeof InputLinkNode, options: InputLinkNodeOptions }, key: string, uuid?: string){
        uuid || (uuid = Uuid())
        // 变量名称
        const inputLinkNode = new item.class(this.graphNode, this.graphView as GraphView, {...item.options, name: uuid as string})
        inputLinkNode.uuid = uuid
        this.container?.appendChild(inputLinkNode)

        inputLinkNode.input?.bindValue( this.data , uuid, (e:any)=>{
            setTimeout(()=>this.update())
            return true
        })

        inputLinkNode.userData.key = key
        inputLinkNode.userData.uuid = uuid
        inputLinkNode.userData.type = item.options.type
        inputLinkNode.userData.name = item.options.name

        this.list.push(inputLinkNode)
        this.data.list.push(key)

        this.update()

        this.itemEvent(inputLinkNode)
        return inputLinkNode
    }

    private update(){
        (this.graphNode.userData as any)[this.options.name] = this.list.map(inputLinkNode=>{
            return {
                key: inputLinkNode.userData.key,
                uuid: inputLinkNode.userData.uuid,
                type: inputLinkNode.options.type,
                data: inputLinkNode.getValue()
            }
        })
    }

    private itemEvent(inputLinkNode: InputLinkNode){
        inputLinkNode.addEventListener("mousedown", ({clientX, clientY, button}: any)=>{
            button === 2 && new Menu([
                {
                    items: [
                        {
                            style:{
                                fontSize: '12px',
                                minWidth: '100px',
                                padding: '2px 5px'
                            },
                            name: "删除",
                            click: ({close}:any)=>{
                                inputLinkNode.removeTo()
                                const i = this.list.indexOf(inputLinkNode)
                                this.list.splice(i, 1)
                                this.update()
                                close()
                            }
                        }
                    ]
                }
            ]).show(clientX, clientY)
        })
    }

    import(list = []){
        list.forEach((item:any) => {
            if(this.inputLinkNodeClassList[item.key]){
                const inputLinkNode = this.add(this.inputLinkNodeClassList[item.key], item.key, item.uuid )
                inputLinkNode.input?.setValue(item.data)
            }
            else{
                console.warn(`不存在连接节点模板：${item.key}`)
            }
        })
    }
}
